/*
 *  Created on: Jun 13, 2013
 *  Author: Heinrich Strauss <heinrich@hstrauss.co.za>
 *  This software is licenced under the GPL v2.0 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt>
 */

//#include <stdio.h>
#include <cstring>
#include <ctime>
#include <sys/stat.h>
#include <assert.h>
//#include <omp.h>
#include <thread>
#include <mutex>
#include "h/zledefs.h"
#include "h/zletimer.h"
#include "h/zlecmp.h"

#include <sstream>

//FOR RAW FILE ACCESS
#include <string>
#include <cstdio>
#include <cerrno>

#define CUDA_CHECK_RETURN(value) {											\
		cudaError_t _m_cudaStat = value;										\
		if (_m_cudaStat != cudaSuccess) {										\
			fprintf(stderr, "Error %s at line %d in file %s\n",					\
					cudaGetErrorString(_m_cudaStat), __LINE__, __FILE__);		\
					exit(1);															\
		} }

// GLOBALS DECLARATION

size_t get_size_of_file(std::string p_filename){
	std::FILE* rawfile = std::fopen(p_filename.c_str(), "r");

	if (rawfile)
	{
		std::fseek(rawfile,0,SEEK_END); // 0 = origin
		size_t ret = ftell(rawfile);
		std::fclose(rawfile);
		return ret;
	}
	fprintf(stderr,"Could not open data file for reading.\n");
	exit(-1);
	//	throw(errno);

}

void read_indices(uint64_t* p_dataBlock_offsets, uint64_t* p_dataBlock_sizes, uint64_t p_numBlocks){ // fixme
	std::FILE* rawfile;
	rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "rb");
	if (rawfile)
	{
		uint64_t pos = 0;
		uint64_t readints[2];
		std::fseek(rawfile,pos,SEEK_SET); // 0 = origin
		for (uint64_t i = 0; i < p_numBlocks; ++i){
			p_dataBlock_sizes[i] = p_dataBlock_offsets[i] = 0;
//assert(0==
		std::fread(&readints, 2, 8, rawfile);
//);
			p_dataBlock_sizes[i]   = readints[0];
			p_dataBlock_offsets[i] = readints[1]; // FIXME: use of const
			//			fprintf(stdout,"Read From File Index Pair (sz,ind) =  {%lu,%lu} (pos:%lu)\n",readints[0],readints[1],pos);
			pos+=p_dataBlock_sizes[i];
			std::fseek(rawfile,pos,SEEK_SET); // 0 = origin
		}

		std::fclose(rawfile);

		/*
		for (uint64_t i = 0; i < p_numBlocks; ++i){
			fprintf(stdout,"\n\t{%lu, o:%lu, s:%lu}",i,p_dataBlock_offsets[i],p_dataBlock_sizes[i]);
		}
		fprintf(stdout,"\n");
		fflush(stdout);
		//		exit(0);
		 */

		return;
	}
	throw(errno);
}

void read_block_from_input_file(std::string p_filename, void* p_block, uint64_t p_blockSize, uint64_t p_offset){
	std::FILE* rawfile;
	rawfile = std::fopen(p_filename.c_str(), "rb");
	if (rawfile)
	{
		std::rewind(rawfile);
		std::fseek(rawfile,p_offset,SEEK_SET); // 0 = origin
//assert(0==
		std::fread(p_block, 1, p_blockSize, rawfile);
//);
		std::fclose(rawfile);
		return;
	}
	throw(errno);


}
void read_block_from_compressed_file(void* p_block, uint64_t p_blockSize, uint64_t p_offset){
	std::FILE* rawfile;
	rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "rb");
	if (rawfile)
	{
		std::rewind(rawfile);
		std::fseek(rawfile,p_offset,SEEK_SET); // 0 = origin
//assert(0==
		std::fread(p_block, 1, p_blockSize, rawfile);
//);
		std::fclose(rawfile);
		return;
	}
	throw(errno);
}
void clobber_uncompressed_file(){
	std::FILE* rawfile;

	rawfile = std::fopen(CONST_RAW_FILENAME_UOUT, "wb");
	std::fflush(rawfile);
	std::fclose(rawfile);
	return;

	throw(errno);

}
void clobber_compressed_file(){
#ifdef DEBUG_NO_CMP_WRITING
	return;
#endif
	std::FILE* rawfile;

	rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "wb");
	std::fflush(rawfile);
	std::fclose(rawfile);
	return;

	throw(errno);

}
void write_block_to_uncompressed_file(void* p_block, uint64_t p_blockSize, uint64_t p_offset){
	std::FILE* rawfile;
	rawfile = std::fopen(CONST_RAW_FILENAME_UOUT, "ab");

	if (rawfile)
	{
//assert(
		std::fseek(rawfile,p_offset,SEEK_SET);
//==0); // 0 = origin
		std::fwrite(p_block, 1, p_blockSize, rawfile);
		std::fflush(rawfile);
		std::fclose(rawfile);
		return;
	}
	throw(errno);

}
void write_block_to_compressed_file(void* p_block, uint64_t p_blockSize, uint64_t p_offset){
#ifdef DEBUG_NO_CMP_WRITING
	return;
#endif
	std::FILE* rawfile;
	if (p_offset == 0){
		rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "w");
		std::fclose(rawfile);
		rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "wb"); // clobber file during first run
	}
	else
		rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "ab");

	if (rawfile)
	{
		std::fseek(rawfile,p_offset,SEEK_SET); // 0 = origin
		std::fwrite(p_block, p_blockSize, 1, rawfile);
		std::fflush(rawfile);
		std::fclose(rawfile);
		return;
	}
	throw(errno);

}
void write_block_to_uncompressed_file_mp(void* p_block, uint64_t p_blockSize, uint64_t p_offset){

	std::FILE* rawfile;
	size_t current_size = get_size_of_file(std::string(CONST_RAW_FILENAME_UOUT));

	//if (current_size < p_offset)
	//	rawfile = std::fopen(CONST_RAW_FILENAME_UOUT, "ab");
	//else
	rawfile = std::fopen(CONST_RAW_FILENAME_UOUT, "r+");

	if (rawfile)
	{
//assert(
		std::fseek(rawfile,p_offset,SEEK_SET);
//==0); // 0 = origin
		std::fwrite(p_block, 1, p_blockSize, rawfile);
		std::fflush(rawfile);
		std::fclose(rawfile);
		return;
	}
	throw(errno);


}
void write_block_to_compressed_file_mp(void* p_block, uint64_t p_blockSize, uint64_t p_offset){
#ifdef DEBUG_NO_CMP_WRITING
	return;
#endif
	std::FILE* rawfile;
	rawfile = std::fopen(CONST_RAW_FILENAME_COUT, "r+");

	if (rawfile)
	{
assert(
		std::fseek(rawfile,p_offset,SEEK_SET)==0
		); // 0 = origin
		//		assert(
		//				fprintf(stderr,"\nWROTE %lu BYTES\n.",
//assert(
		std::fwrite(p_block, 1, p_blockSize, rawfile);
//== p_blockSize);
		//				);
		//		== p_blockSize);
		std::fflush(rawfile);
		std::fclose(rawfile);
		return;
	}
	throw(errno);

}
inline void print_time(const uint64_t p_time_in_nanos, const uint64_t p_inputSize, const uint64_t p_outputSize){

	if (p_time_in_nanos < double(1000)) {
		fprintf(stdout, "\n%lu ns taken.", p_time_in_nanos);
	} else if (p_time_in_nanos < double(1000.0) * 1000) {
		fprintf(stdout, "\n%lu us taken.", p_time_in_nanos / 1000);
	} else if (p_time_in_nanos < double(1000.0) * 1000 * 1000) {
		fprintf(stdout, "\n%lu ms taken.", p_time_in_nanos / 1000000);
	} else {
		fprintf(stdout, "\n%lu s taken.", p_time_in_nanos / 1000000000);
	}

	double iSpeed = ( p_inputSize/CONST_FACTOR_MEGA) / ((double)(p_time_in_nanos/CONST_FACTOR_ns));
	double oSpeed = (p_outputSize/CONST_FACTOR_MEGA) / ((double)(p_time_in_nanos/CONST_FACTOR_ns)); // X-XX: get speed in MB/s

	fprintf(stdout,"\t[R:%0.4f MB/s]\t[W:%0.4f MB/s]\n",iSpeed,oSpeed);
	fprintf(stdout,"\t\t[R:%lu B]\t[W:%lu B]\n",p_inputSize,p_outputSize);
	fflush(stdout);

	//	fprintf(stdout,"\n%lu nanoseconds taken.\n",p_time_in_nanos);
}

inline void print_compression_ratio(const uint64_t p_in_sizeBytes,const uint64_t p_out_sizeBytes){
	fprintf(stdout,"\nRatio: %0.4f (%lu/%lu bytes processed)\n",(p_in_sizeBytes/((double)p_out_sizeBytes)),p_in_sizeBytes, p_out_sizeBytes);
}


void do_naive_compression(std::string p_filename,void* p_input, void* p_output, const uint64_t p_sizeBytes, const uint64_t p_blockSize, uint64_t* p_outputSize){
	fprintf(stdout,"\n////////////////////////////////////////\n//\tNaive COMPRESSION\n");
	//	return;
	uint64_t t_total = 0;
	uint64_t numBlocks = p_sizeBytes / p_blockSize;

	uint64_t block_output_size_bytes = 1;

	uint64_t block_index = 0;

	clobber_compressed_file();

	bool partialBlock = (p_sizeBytes % p_blockSize) != 0;
	uint64_t block_uncompressed_offset = 0;

	uint64_t total_compressed_size = 0;

	for (; block_index < numBlocks; ++block_index){
//		printdb2("\nUncompressed Offset: %lu ",block_uncompressed_offset);
		read_block_from_input_file(p_filename,p_input,p_blockSize,block_index*p_blockSize);
		zletimer::start();
		zlecmp::zcompress(block_index,p_blockSize,&block_uncompressed_offset,&block_output_size_bytes,p_input,p_output);
		t_total += zletimer::stop();
		write_block_to_compressed_file(p_output,block_output_size_bytes,total_compressed_size);

//assert(block_output_size_bytes > 8);
		total_compressed_size += block_output_size_bytes;
		*p_outputSize += block_output_size_bytes;
	}
	if (partialBlock||block_uncompressed_offset){
//assert((block_uncompressed_offset+(p_sizeBytes%p_blockSize) < p_blockSize));
//		printdb2("\nblock_uncompressed_offset = %lu\n",block_uncompressed_offset);
		read_block_from_input_file(p_filename,p_input,((block_uncompressed_offset+p_sizeBytes) % p_blockSize),block_index*p_blockSize);
		zletimer::start();
		zlecmp::zcompress(block_index,((block_uncompressed_offset+p_sizeBytes) % p_blockSize),&block_uncompressed_offset,&block_output_size_bytes,p_input,p_output);
		t_total += zletimer::stop();

		write_block_to_compressed_file(p_output,block_output_size_bytes,total_compressed_size);
		total_compressed_size += block_output_size_bytes;
		*p_outputSize += block_output_size_bytes;

	}
	*p_outputSize += 8; // 8 caters for final index
	uint64_t GUARD = 0x00000000;
	write_block_to_compressed_file(&GUARD,4,*p_outputSize);
	fprintf(stdout,"\nTime taken for compression routine only:\n");
	print_time(t_total,p_sizeBytes,*p_outputSize);
	print_compression_ratio(total_compressed_size,p_sizeBytes);

}


void do_naive_decompression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes, const uint64_t p_blockSize, uint64_t* p_outputSize){
	fprintf(stdout,"\n////////////////////////////////////////\n//\tNaive DE-COMPRESSION\n");
	//	return;
	int8_t boundary_offset = 0;

	uint64_t t_total = 0;

	uint64_t block_output_size_bytes = 1;

	clobber_uncompressed_file();

	uint64_t block_index = 0;
	uint64_t bytesProcessed = 0;

	uint64_t d_block_begin = 0;
	uint64_t d_block_length;
	read_block_from_compressed_file(p_input,sizeof(d_block_length),0);
	d_block_length = ((uint64_t*)p_input)[0];

	uint64_t index_input = 0;
	uint64_t index_output = 0;

//assert(d_block_length > 0); // check if first uint64_t is 0 (error: end of file?)

	byte* b_input = (byte*)p_input;
	byte* b_output = (byte*)p_output;

	block_output_size_bytes = p_blockSize;

	read_block_from_compressed_file(p_input,p_blockSize,0);

	while((d_block_length !=0)) {

		zletimer::start();
		zlecmp::zdecompress(block_index,d_block_length,p_blockSize,&block_output_size_bytes,p_input,p_output);
		t_total += zletimer::stop();

		index_input += d_block_length;
		write_block_to_uncompressed_file(p_output,block_output_size_bytes,index_output);
		index_output+= block_output_size_bytes;

		d_block_begin += d_block_length;
		read_block_from_compressed_file(p_input,p_blockSize,index_input);
		d_block_begin = index_input+CONST_SIZE_C_FILE_HEAD;
		//		}
		d_block_length = ((uint64_t*)p_input)[0];

		bytesProcessed+=block_output_size_bytes; // output size
		bytesProcessed += d_block_length;
		block_index++;

	}
	block_output_size_bytes--;
	*p_outputSize = block_output_size_bytes;
	fprintf(stdout,"\nTime taken for decompression routine only:\n");
	print_time(t_total,index_input,index_output);

}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////   END OF NAIVE

//void do_mp_compression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes, const uint64_t p_blockSize, uint64_t* p_outputSize,
//		uint64_t* p_dataBlock_offsets, uint64_t* p_dataBlock_sizes, uint64_t p_numBlocks){
//	fprintf(stdout,"\n////////////////////////////////////////\n//\tMP DE-COMPRESSION\n");
//
//}
uint64_t get_thread_block_offset(std::mutex* p_write_mutex,const uint64_t* p_dataBlock_sizes, const uint64_t p_thread_id){
	uint64_t ret = 0;
	for (uint64_t i = 1; i <= p_thread_id; ++i){
		if(p_dataBlock_sizes[i] == 0)
			i=1;
	}
	p_write_mutex->lock();{
		for (uint64_t i = 0; i < p_thread_id; ++i){
			ret+=p_dataBlock_sizes[i];
		}
	}p_write_mutex->unlock();
	return ret;
}

void threadable_mp_compression(const uint64_t p_thread_id, uint64_t p_blkid, std::string p_filename, std::mutex *p_write_mutex,
		const uint64_t p_data_actual_size_u, const uint64_t p_dataBlock_size_u, uint64_t *p_thread_output_size,
		uint64_t* p_dataBlock_sizes, uint64_t* p_dataBlock_offsets,uint64_t* total_written,uint64_t* total_time){

	// Must declare new memory space to prevent adjacent threads from clobbering it.
	char* thread_input = new char[p_dataBlock_size_u];
	char* thread_output = new char[p_dataBlock_size_u*2+CONST_SIZE_C_FILE_HEAD]; // f-bomb + index

	uint64_t thread_block_uncompressed_offset = p_thread_id*p_dataBlock_size_u;
	uint64_t thread_block_offset = 0;
	uint64_t thread_write_offset = 0;

	uint64_t thread_time;


	read_block_from_input_file(p_filename,thread_input,p_data_actual_size_u,thread_block_uncompressed_offset);

	//	compress
	//	zletimer::start();
	timespec tstart, tstop;
	clock_gettime( CLOCK_MONOTONIC, &tstart);
	zlecmp::zcompress(p_thread_id,p_data_actual_size_u,&thread_block_uncompressed_offset,p_thread_output_size,thread_input,thread_output);
	//	thread_time = zletimer::stop();
	clock_gettime( CLOCK_MONOTONIC, &tstop);
	thread_time = (tstop.tv_sec - tstart.tv_sec) * (1000 * 1000 * 1000L) + (tstop.tv_nsec - tstart.tv_nsec);

	//update our size
	p_write_mutex->lock();{
		p_dataBlock_sizes[p_thread_id] = *p_thread_output_size;
		//		p_dataBlock_offsets[p_thread_id] = p_thread_id*p_blockSize;
		thread_write_offset = *total_written;
		*total_written += *p_thread_output_size;
		*total_time += thread_time;
	}p_write_mutex->unlock();

	//	write to file (atomic);
	p_write_mutex->lock();{
//assert(*p_thread_output_size < p_dataBlock_size_u*2+CONST_SIZE_C_FILE_HEAD);
		write_block_to_compressed_file_mp(thread_output,*p_thread_output_size,thread_write_offset);

	}p_write_mutex->unlock();


	delete[] thread_output;
	delete[] thread_input;

}
void do_mp_compression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes, const uint64_t p_blockSize, uint64_t* p_outputSize,
		uint64_t* p_dataBlock_offsets, uint64_t* p_dataBlock_sizes, uint64_t p_numBlocks){
	// TODO: [SEGMENT] OpenMP Compression
	fprintf(stdout,"\n////////////////////////////////////////\n//\tMP COMPRESSION\n");
	//		return;

	std::mutex write_mutex;
	uint64_t numBlocks = p_sizeBytes / p_blockSize;
	uint64_t block_output_size_bytes = 1;
	uint64_t i = 0, j = 0; // loop over blocks as well as partial block
	uint64_t t_total = 0;
	uint64_t t_io_total = 0;

	uint64_t num_thread_batch = CONST_NUM_THREADS;

	clobber_compressed_file();

	bool partialBlock = (p_sizeBytes % p_blockSize) != 0;

	uint64_t block_uncompressed_offset = 0;
	uint64_t total_compressed_size = 0;
	//

	std::thread threads[numBlocks];

	// PREALLOCATE INPUT AND OUTPUT MEMORY
	//	char* threads_block_input  = new char[CONST_NUM_THREADS*p_blockSize];
	//	char* threads_block_output = new char[CONST_NUM_THREADS*(p_blockSize*2+CONST_SIZE_C_FILE_HEAD)]; // FF_



	uint64_t compressed_block_size[numBlocks+(partialBlock?1:0)];

	zletimer::start();
	/*	for (uint64_t j=0; j < numBlocks/CONST_NUM_THREADS; ++j){ // process CONST_NUM_THREADS AT A TIME
		for (i=0; i < CONST_NUM_THREADS; ++i){
			threads[i] = std::thread(threadable_mp_compression,p_filename,p_input, p_output, p_blockSize,  p_blockSize, p_outputSize,
					&block_uncompressed_offset,&block_output_size_bytes,
					i,(i*p_blockSize),&write_mutex,p_dataBlock_sizes,p_dataBlock_offsets,&total_compressed_size,p_numBlocks);
		}

		for (i=0; i < CONST_NUM_THREADS; ++i){
			threads[i].join();
		}
	}
	for (uint64_t i=0; i < numBlocks%CONST_NUM_THREADS; ++i){ // process numblocks % CONST_NUM_THREADS remaining
		threads[i] = std::thread(threadable_mp_compression,p_filename,p_input, p_output, p_blockSize,  p_blockSize, p_outputSize,
				&block_uncompressed_offset,&block_output_size_bytes,
				i,(i*p_blockSize),&write_mutex,p_dataBlock_sizes,p_dataBlock_offsets,&total_compressed_size,p_numBlocks);
	}

	for (i=0; i < numBlocks%CONST_NUM_THREADS; ++i){
		threads[i].join();
	}*/

	//complete input blocks only
	for (j=0; j < numBlocks/num_thread_batch; ++j){
		for (i=j*num_thread_batch; i < (j+1)*num_thread_batch; ++i){
			threads[i] = std::thread(threadable_mp_compression,i,i, p_filename, &write_mutex,
					p_blockSize, p_blockSize, &compressed_block_size[i], p_dataBlock_sizes, p_dataBlock_offsets,&total_compressed_size,&t_total);
		}

		for (i=j*num_thread_batch; i < (j+1)*num_thread_batch; ++i){
			threads[i].join();
		}
	}

	// if num_thread_batch does not evenly divide numBlocks, do the straddlers
	for (i=j*num_thread_batch; i < numBlocks; ++i){
		threads[i] = std::thread(threadable_mp_compression,i,i, p_filename, &write_mutex,
				p_blockSize, p_blockSize, &compressed_block_size[i], p_dataBlock_sizes, p_dataBlock_offsets,&total_compressed_size,&t_total);
	}

	for (i=j*num_thread_batch; i < numBlocks; ++i){
		threads[i].join();
	}

	t_io_total += zletimer::stop();

	//single incomplete input block only
	if (partialBlock){//||block_uncompressed_offset){ // process maximum singular compressed block
//		printdb2("Now doing partial Block... p_sizeBytes (mod) p_blockSize = %lu\n",p_sizeBytes % p_blockSize);
//		printdb2("numBlocks = %lu\n",numBlocks);
//		printdb2("block_uncompressed_offset = %lu\n",block_uncompressed_offset);
		i=numBlocks; // partialBlock
		zletimer::start();

		threadable_mp_compression(i,i, p_filename, &write_mutex,
				p_sizeBytes%p_blockSize, p_blockSize, &compressed_block_size[i], p_dataBlock_sizes, p_dataBlock_offsets,&total_compressed_size,&t_total);


		t_io_total += zletimer::stop();
	}
//	printdb1("done!\n");
	//	*p_outputSize += 8; // 8 caters for final index
	total_compressed_size += 8; // 8 caters for final index

	uint32_t GUARD = 0x00000000;
	write_block_to_compressed_file(&GUARD,4,total_compressed_size);
	*p_outputSize = total_compressed_size;
	fprintf(stdout,"\nTime taken for compression routine only:\n");
	print_time(t_total/num_thread_batch,p_sizeBytes,total_compressed_size);
	fprintf(stdout,"\nTime taken including Disk I/O:\n");
	print_time(t_io_total,p_sizeBytes,total_compressed_size);
	print_compression_ratio(total_compressed_size,p_sizeBytes);
}

void threadable_mp_decompression(const uint64_t p_thread_id, uint64_t p_blkid, std::string p_filename, std::mutex *p_write_mutex,
		const uint64_t p_dataBlock_offset_c, const uint64_t p_dataBlock_size_c, const uint64_t p_blockSize_u, uint64_t* t_total){

	timespec tstart,tstop;
	uint64_t thread_time = 0;

	if(p_dataBlock_size_c == 0)
		return;

	// Must declare new memory space to prevent adjacent threads from clobbering it.
	byte* thread_input = new byte[p_dataBlock_size_c];
	byte* thread_output = new byte[p_blockSize_u];

	uint64_t* input64_t = (uint64_t*)thread_input;
	uint64_t* output64_t = (uint64_t*)thread_output;

	uint64_t p_blockSize_deco = 0;

	p_write_mutex->lock();{
		read_block_from_compressed_file(thread_input,p_dataBlock_size_c,p_dataBlock_offset_c);
	}p_write_mutex->unlock();

	//	zletimer::mstart();
	clock_gettime( CLOCK_MONOTONIC, &tstart);
	zlecmp::zdecompress(p_blkid, p_dataBlock_size_c, p_blockSize_u, &p_blockSize_deco, thread_input, thread_output);
	//	thread_time = zletimer::mstop();
	clock_gettime( CLOCK_MONOTONIC, &tstop);
	thread_time = (tstop.tv_sec - tstart.tv_sec) * (1000 * 1000 * 1000L) + (tstop.tv_nsec - tstart.tv_nsec);

	p_write_mutex->lock();{
		write_block_to_uncompressed_file_mp(thread_output,p_blockSize_deco,p_blkid*p_blockSize_u);
		*t_total += thread_time;
	}p_write_mutex->unlock();

	delete[] thread_input;
	delete[] thread_output;

}

void do_mp_decompression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes, const uint64_t p_blockSize,
		uint64_t* p_outputSize,  uint64_t* p_dataBlock_offsets, uint64_t* p_dataBlock_sizes, uint64_t p_numBlocks){
	fprintf(stdout,"\n////////////////////////////////////////\n//\tMP DE-COMPRESSION\n");

	uint64_t t_total_with_io = 0;
	uint64_t t_total = 0;

	uint64_t num_thread_batch = CONST_NUM_THREADS;

	read_indices(p_dataBlock_offsets,p_dataBlock_sizes,p_numBlocks);
	clobber_uncompressed_file();

	uint64_t blkids[p_numBlocks];

//	printdb1("\nAssigning blockids from offset indices");
	for(uint64_t c = 0; c < p_numBlocks; ++c){
		blkids[c] = p_dataBlock_offsets[c];
	}

//	printdb1("\nGenerating Offsets from sizes");
	p_dataBlock_offsets[0] = 0;
	for(uint64_t c = 1; c < p_numBlocks; ++c){
		if (p_dataBlock_sizes[c] == 0)
			continue;
		p_dataBlock_offsets[c] = p_dataBlock_offsets[c-1]+ p_dataBlock_sizes[c-1];

	}

	//	printdb1("\nAssigning blockids from offset indices");
	/*	for(uint64_t i = 0; i < p_numBlocks; ++i){
		fprintf(stdout,"\n\tmmmi\tC:[%lu,%lu] must map to U:[%lu,%lu] (block %lu) by th:%lu\n",
				p_dataBlock_offsets[i],p_dataBlock_offsets[i]+p_dataBlock_sizes[i]-1,
				blkids[i]*p_blockSize,(blkids[i]+1)*p_blockSize-1,
				blkids[i],i);
	}


	for(uint64_t c = 0; c < p_numBlocks; ++c){
		fprintf(stdout,"\n{c:%lu\tblkids[c]:%lu\toffset:%lu}",c,blkids[c],p_dataBlock_offsets[c]);
	}
	fprintf(stdout,"\n");
	 */


	std::mutex write_mutex;
	uint64_t i=0, j=0;
	std::thread threads[p_numBlocks];
	zletimer::start();

	//complete input blocks only
		for (j=0; j < p_numBlocks/num_thread_batch; ++j){
			for (i=j*num_thread_batch; i < (j+1)*num_thread_batch; ++i){
				threads[i] = std::thread(threadable_mp_decompression, i, blkids[i], p_filename, &write_mutex,
						p_dataBlock_offsets[i],p_dataBlock_sizes[i],p_blockSize,&t_total);
			}

			for (i=j*num_thread_batch; i < (j+1)*num_thread_batch; ++i){
				threads[i].join();
			}
		}

		// if num_thread_batch does not evenly divide numBlocks, do the straddlers
			for (i=j*num_thread_batch; i < p_numBlocks; ++i){
				threads[i] = std::thread(threadable_mp_decompression, i, blkids[i], p_filename, &write_mutex,
						p_dataBlock_offsets[i],p_dataBlock_sizes[i],p_blockSize,&t_total);	}

		for (i=j*num_thread_batch; i < p_numBlocks; ++i){
			threads[i].join();
		}

	t_total_with_io = zletimer::stop();

	fprintf(stdout,"\nTime taken for decompression routine only:\n");
	print_time(t_total/num_thread_batch, p_sizeBytes,get_size_of_file(p_filename));
	fprintf(stdout,"\nTime taken including Disk I/O:\n");
	print_time(t_total_with_io, p_sizeBytes,get_size_of_file(p_filename));
}
extern void do_cuda_compression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes);

extern void do_cuda_decompression(std::string p_filename, void* p_input, void* p_output, const uint64_t p_sizeBytes);

int main(int argc, char**argv){

#ifndef DEBUG_ARGS
	if (argc == 3) {
#endif

		// GLOBALS INITIALIZATION // X-XX: check if globals necessary
		//		ztimer = new zletimer();

		// ALLOCATE MEMORY
		//
		// u_ variables are for uncompressed data
		// c_ variables are for the results of compression
		// d_ variables are for the results of decompression
		//
		// _h pointers are for host-memory
		// _d pointers are for device-memory (CUDA only)

		uint64_t u_dataBlock_size = CONST_DEFAULT_BLOCK_SIZE_BYTES;
		uint64_t c_dataBlock_size = CONST_DEFAULT_BLOCK_SIZE_BYTES*2+CONST_SIZE_C_FILE_HEAD; // FF-bomb + uint64_t(length)

		void* u_dataBlock_h = (void*)new byte[u_dataBlock_size];
		void* c_dataBlock_h = (void*)new byte[c_dataBlock_size];
		void* d_dataBlock_h = (void*)new byte[u_dataBlock_size];

		std::string filename(argv[1]);

		uint64_t file_sizeBytes = get_size_of_file(filename);
		//	    uint64_t file_sizeBytes = CONST_RAW_FILE_SIZE;

		uint64_t u_sizeBytes = file_sizeBytes; // TO-DO: get proper data-set size
		uint64_t c_sizeBytes = 0;
		uint64_t d_sizeBytes = 0; //(1<<24); // TO-DO: remove initialization of d_sizeBytes

		u_dataBlock_size = c_dataBlock_size = (CONST_DEFAULT_BLOCK_SIZE_BYTES); //X-XX [OPTIMISE] define decent buffer size

		// RUN TIME FLAGS FOR ALGORITHMS TO RUN, Bitmap: 1 = CPU (Naive), 2 = CPU (Optimized/MP), 4 = GPU (CUDA)
		byte runflags = atoi(argv[2]); // X-XX: read runflags from commandline
#ifdef DEBUG_ARGS
//				filename = std::string("data/32mb.fake.dump");
//				runflags = 4;
#endif
assert(runflags < 8);

		uint64_t numBlocks = (file_sizeBytes/CONST_DEFAULT_BLOCK_SIZE_BYTES)+1;
		uint64_t* c_dataBlock_offsets = new uint64_t[numBlocks];
		uint64_t* c_dataBlock_sizes = new uint64_t[numBlocks];

		fprintf(stdout,"\n***\tRunning routines on \"%s\"; File Size: %lu bytes.",filename.c_str(),file_sizeBytes);
		fprintf(stdout,"\n***\tRepeating %d time%s with compression block size of %d bytes.",CONST_NUMBER_OF_RUNS,(CONST_NUMBER_OF_RUNS!=1)?"s":"",CONST_DEFAULT_BLOCK_SIZE_BYTES);

		// DO THE COMPRESSION AND DECOMPRESSION ALGORITHMS
		for (uint64_t loop_iterations = 0; loop_iterations < CONST_NUMBER_OF_RUNS; ++loop_iterations){

			if (runflags%2==1){  // 0000000X indicates CPU compression (naive)
				do_naive_compression(filename,u_dataBlock_h, c_dataBlock_h, u_sizeBytes, u_dataBlock_size, &c_sizeBytes);
//								printdb2("\n///////\nc_sizeBytes = %lu\n///////\n",c_sizeBytes);
				do_naive_decompression(filename,c_dataBlock_h, d_dataBlock_h, c_sizeBytes, c_dataBlock_size, &d_sizeBytes);
				//assert (u_sizeBytes == d_sizeBytes);
				//				print_compression_ratio(c_sizeBytes,d_sizeBytes);
			}
			if ((runflags>>1)%2==1){ // 000000X0 indicates CPU compression (MP)
				do_mp_compression(filename,u_dataBlock_h, c_dataBlock_h, u_sizeBytes, u_dataBlock_size, &c_sizeBytes,
						c_dataBlock_offsets, c_dataBlock_sizes, numBlocks);
				do_mp_decompression(filename,c_dataBlock_h, d_dataBlock_h, c_sizeBytes, c_dataBlock_size, &d_sizeBytes,
						c_dataBlock_offsets, c_dataBlock_sizes, numBlocks);
			}
			if ((runflags>>2)%2==1){ // 00000X00 indicates GPU compression (CUDA)
				do_cuda_compression(filename,u_dataBlock_h, c_dataBlock_h, u_dataBlock_size);
				do_cuda_decompression(filename,c_dataBlock_h, d_dataBlock_h, c_dataBlock_size);
			}
			fprintf(stdout,"\n");
		}

//		printdb2("\n\nUsing CPU Compression algorithm %d.\n\n",CONST_ALG);

		// DEALLOCATE USED MEMORY
assert(u_dataBlock_h != nullptr);
assert(c_dataBlock_h != nullptr);
assert(d_dataBlock_h != nullptr);
		//assert(reader != nullptr);
		//		free(u_dataBlock_h);
		//		free(c_dataBlock_h);
		//		free(d_dataBlock_h);
		delete[] (byte*)u_dataBlock_h;
		delete[] (byte*)c_dataBlock_h;
		delete[] (byte*)d_dataBlock_h;
		delete[] c_dataBlock_offsets;
		delete[] c_dataBlock_sizes;

		//		delete reader;

		fprintf(stdout,"\nCalling for MD5 hashes of input and output:\n");
		fflush(stdout);
		int result = std::system( std::string("md5sum " +filename).c_str());
		if(!(result))
			result = std::system((std::string("md5sum ")+std::string(CONST_RAW_FILENAME_UOUT)).c_str());

#ifndef DEBUG_ARGS
	}
	else {
		fprintf(stderr,"ZLE Compressor for HDF5 (SKA; KAT-7; MeerKAT)\n---------------------------------------------\n");

		fprintf(stderr,"\n\tERROR: Too few parameters; please use ./cudasqueeze-zle <DATA_FILE> <FLAGS>\nFLAGS is a bitmap for the implementations used.\n\t\t1=Naive CPU,\n\t\t2=Optimized CPU,\n\t\t4=GPU (CUDA)\n"); // X-XX: Prettify cmdline Usage Notes
		fprintf(stderr,"./cudasqueeze-zle ../data/datafile.dump 5\n\t= Run Compressor on \"datafile.dump\" on Naive CPU (1) and GPU (4)\n\n");
		fflush(stderr);
	}
#endif
	return 0;
}
